﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Sockets;
using System.Net;
using System.Threading;
using System.Text;


namespace iPhoneConnection
{
    public class iPhone
    {
        public delegate void DataRecieved();
        public event DataRecieved dataRecieved;
        public delegate void iPhoneConnected();
        public event iPhoneConnected connected;
        public delegate void iPhoneDisconnected();
        public event iPhoneDisconnected disconnected;

        #region Private Variables

        private TcpListener tcpListener;
        private UnicodeEncoding encoding;
        private TcpClient client;
        private Dictionary<int, Sensors> sensorOrder;

        private bool shuttingDown = false;
        private int timeout;

        #endregion

        #region Public Variables

        public Settings connectionSettings;
        public Accelerometer AccelerometerReading;
        public GPS GPSReading;
        public Device DeviceReading;
        public Compass CompassReading;
        public Gyro GyroReading;

        #endregion

        #region Enums

        [Flags]
        public enum Sensors
        {
            Proximity_Sensors = 0x1,
            Device_Orientation = 0x2,
            Accelerometer_X = 0x4,
            Accelerometer_Y = 0x8,
            Accelerometer_Z = 0x10,
            Battery_State = 0x20,
            Battery_Percent = 0x40,
            Location_X = 0x80,
            Location_Y = 0x100,
            Location_Z = 0x200,
            Location_Accuracy = 0x400,
            Location_Heading = 0x800,
            Location_Speed = 0x1000,
            Compass_Magnetic = 0x2000,
            Compass_True_Heading = 0x4000,
            Compass_X = 0x8000,
            Compass_Y = 0x10000,
            Compass_Z = 0x20000,
            Gyro_X = 0x40000,
            Gyro_Y = 0x80000,
            Gyro_Z = 0x100000
        }
        public enum DescriptionModes : int
        {
            NO_DESCRIPTIONS = 0,
            DESCRIPTIONS_FIRST = 1,
            DESCRIPTIONS_ALWAYS = 2
        }
        public enum DeviceOrientation : int
        {
            Unknown = 0,
            Portrait,
            PortraitUpsideDown,
            LandscapeLeft,
            LandscapeRight,
            FaceUp,
            FaceDown
        }
        public enum BatteryState :int
        {
            Unknown,Discharging,Charging,Full
        }

        private enum SensorUpdateTypes { Device, Accelerometer, Location, Compass, Gyro }

        #endregion

        #region Strucs

        public struct Settings
        {
            public bool plainText;
            public DescriptionModes descriptionMode;
            public bool usesDouble;
            public List<Sensors> enabledSensors;
        }
        public struct Accelerometer
        {
            public double X;
            public double Y;
            public double Z;
        }
        public struct GPS
        {
            public double X;
            public double Y;
            public double Z;
            public double Accuracy;
            public double Speed;
            public double Heading;
        }
        public struct Device
        {
            public bool proximitySensor;
            public DeviceOrientation orientation;
            public double batteryPercent;
            public BatteryState batteryState;
        }
        public struct Compass
        {
            public double MagneticHeading;
            public double TrueHeading;
            public double X,Y,Z;
        }
        public struct Gyro
        {
            public double X, Y, Z;
        }

        #endregion

        public iPhone(int port)
        {
            GPSReading = new GPS();
            AccelerometerReading = new Accelerometer();
            DeviceReading = new Device();
            CompassReading = new Compass();
            GyroReading = new Gyro();

            tcpListener = new TcpListener(IPAddress.Any, port);
            encoding = new UnicodeEncoding();
            sensorOrder = new Dictionary<int, Sensors>();
        }

        #region Network Code

        public void BeginListening()
        {
            tcpListener.Start();
            tcpListener.BeginAcceptTcpClient(ClientConnected, null);
        }

        public void StopListening()
        {
            tcpListener.Stop();
        }

        private void ClientConnected(IAsyncResult result)
        {
            
            Thread t = new Thread(DataThread);
            try
            {
                client = tcpListener.EndAcceptTcpClient(result);
                t.Start();
                tcpListener.BeginAcceptTcpClient(ClientConnected, null);
            }
            catch
            {
            }
        }

        private void DataThread()
        {
            Stream stream = client.GetStream();

            #region Configuration Data

            byte[] settings = new byte[8];
            stream.Read(settings, 0, 8);

            connectionSettings = new Settings();
            connectionSettings.enabledSensors = new List<Sensors>();
            connectionSettings.plainText = settings[0] == 1;
            connectionSettings.descriptionMode = (DescriptionModes)settings[1];
            connectionSettings.usesDouble = settings[2] == 1;
            timeout = settings[3] + 2;
            long enabledSensors = BitConverter.ToUInt32(settings, 4);

            client.ReceiveTimeout = 1000 * timeout;
            stream.ReadTimeout = 1000 * timeout;

            int sensors = 0;
            for (int i = 0; i < Enum.GetNames(typeof(Sensors)).Length; i++)
            {
                if ((enabledSensors & (1 << i)) == (1 << i))
                {
                    connectionSettings.enabledSensors.Add((Sensors)(1 << i));
                    sensors++;
                }
            }

            byte[] order = new byte[21];
            stream.Read(order, 0, 21);

            int count=0;
            int[] indexes = new int[sensors];
            Sensors[] sensor = new Sensors[sensors];

            sensorOrder.Clear();
            
            for (int i = 0; i < 21; i++)
            {
                if (order[i] != 255 && ((enabledSensors & (1 << i)) == (1 << i)))
                {
                    //sensorOrder.Add(order[i], (Sensors)(1 << i));
                    indexes[count] = order[i];
                    sensor[count] = (Sensors)(1<<i);
                    count++;
                }
            }
            int lowest=0;
            while (lowest != count)
            {
                for (int i = 0; i < sensors; i++)
                {
                    if (indexes[i] > lowest)
                    {
                        indexes[i]--;
                    }
                    if (indexes[i] == lowest)
                    {
                        sensorOrder.Add(lowest, sensor[i]);
                        lowest++;
                        break;
                    }
                }
            }

            #endregion

            #region Data Reading

            if (connected != null)
                connected();

            ReadByteData(stream);

            stream.Close();

            if (disconnected != null)
                disconnected();

            #endregion
        }

        public void ReadByteData(Stream stream)
        {
            List<double> data = new List<double>();
            int counter = 0;
            int amountToRead = sensorOrder.Count * (connectionSettings.usesDouble?10:6);
            while (!shuttingDown && client.Connected)
            {
                byte[] bytes = new byte[amountToRead];

                while (true)
                {
                    counter++;
                    if (client.Available >= amountToRead)
                        break;
                    if (counter > timeout * 130)
                    {
                        stream.Close();
                        client.Close();
                        return;
                    }
                    Thread.Sleep(10);
                }

                counter = 0;

                stream.Read(bytes, 0, amountToRead);

                for (int i = 0; i < bytes.Length; i += (connectionSettings.usesDouble?10:6))
                {
                    byte[] temp = new byte[connectionSettings.usesDouble ? 8 : 4];
                    Buffer.BlockCopy(bytes, i, temp, 0, (connectionSettings.usesDouble ? 8 : 4));
                    data.Add(getValue(temp));
                }

                for (int i = 0; i < data.Count; i++)
                {
                    writeSensor(sensorOrder[i], data[i]);
                }

                if (dataRecieved != null)
                    dataRecieved();

                data.Clear();
            }
        }

        public double getValue(byte[] data)
        {
            double value = -1;
            if (data.Length == 4)
            {
                byte temp1, temp2;
                temp1 = data[0];
                temp2 = data[2];
                data[0] = data[1];
                data[2] = data[3];
                data[1] = temp1;
                data[3] = temp2;
                value = BitConverter.ToSingle(data, 0);
            }
            else
            {

                byte temp1, temp2,temp3,temp4;
                temp1 = data[0];
                temp2 = data[2];
                temp3 = data[4];
                temp4 = data[6];
                data[0] = data[1];
                data[2] = data[3];
                data[4] = data[5];
                data[6] = data[7];
                data[1] = temp1;
                data[3] = temp2;
                data[5] = temp3;
                data[7] = temp4;
                value = BitConverter.ToDouble(data, 0);
            }
            return value;
        }

        public bool getUniChar(Stream strm, out char letter)
        {
            byte[] b = new byte[2];
            int written = (strm.Read(b, 0, 2));
            letter =  encoding.GetChars(b)[0];
            return written != 0;
        }

        #endregion

        public void writeSensor(Sensors sensor, double data)
        {
            switch (sensor)
            {
                case Sensors.Proximity_Sensors: DeviceReading.proximitySensor = data == 1; break;
                case Sensors.Device_Orientation: DeviceReading.orientation = (DeviceOrientation)data; break;

                case Sensors.Accelerometer_X: AccelerometerReading.X = data; break;
                case Sensors.Accelerometer_Y: AccelerometerReading.Y = data; break;
                case Sensors.Accelerometer_Z: AccelerometerReading.Z = data; break;

                case Sensors.Battery_State: DeviceReading.batteryState = (BatteryState)data; break;
                case Sensors.Battery_Percent: DeviceReading.batteryPercent = data; break;

                case Sensors.Location_X: GPSReading.X = data+1; break;
                case Sensors.Location_Y: GPSReading.Y = data+1; break;
                case Sensors.Location_Z: GPSReading.Z = data; break;
                case Sensors.Location_Accuracy: GPSReading.Accuracy = data; break;
                case Sensors.Location_Heading: GPSReading.Heading = data; break;
                case Sensors.Location_Speed: GPSReading.Speed = data; break;

                case Sensors.Compass_Magnetic: CompassReading.MagneticHeading = data; break;
                case Sensors.Compass_True_Heading: CompassReading.TrueHeading = data; break;
                case Sensors.Compass_X: CompassReading.X = data; break;
                case Sensors.Compass_Y: CompassReading.Y = data; break;
                case Sensors.Compass_Z: CompassReading.Z = data; break;

                case Sensors.Gyro_X: GyroReading.X = data; break;
                case Sensors.Gyro_Y: GyroReading.Y = data; break;
                case Sensors.Gyro_Z: GyroReading.Z = data; break;
            }
        }

    }

}